package com.xcity.game.relation;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xcity.db.entity.Build;
import com.xcity.db.entity.Player;
import com.xcity.db.entity.Ranking;
import com.xcity.db.entity.Relation;
import com.xcity.db.entity.Relation2;
import com.xcity.db.entity.User;
import com.xcity.db.mapper.Relation2Mapper;
import com.xcity.db.mapper.RelationMapper;
import com.xcity.game.account.AccountChannel;
import com.xcity.game.common.Cause;
import com.xcity.game.common.GameStaticConfig;
import com.xcity.game.common.MessageConst;
import com.xcity.game.common.PlayerPool;
import com.xcity.game.event.GameEvents;
import com.xcity.game.mail.MailService;
import com.xcity.game.player.PlayerService;
import com.xcity.game.player.PlayerUtils;
import com.xcity.game.player.cache.PlayerCacheService;
import com.xcity.game.player.robot.RobotUtils;
import com.xcity.game.rank.RankingListEnum;
import com.xcity.game.rank.impl.QQFriendsRankingList;
import com.xcity.pomelo.PomeloRoute;
import com.xcity.pomelo.push.msg.DotTipPush;
import com.xcity.pomelo.push.msg.FriendApplyPush;
import com.xcity.pomelo.push.msg.TipPush;
import com.xcity.pomelo.request.AddFriendByShareRequest;
import com.xcity.pomelo.request.AddFriendRequest;
import com.xcity.pomelo.request.ApplyAddFriendRequest;
import com.xcity.pomelo.request.AskPresentStrengthRequest;
import com.xcity.pomelo.request.BatchAgreeFriendApplyRequest;
import com.xcity.pomelo.request.BatchRefuseFriendApplyRequest;
import com.xcity.pomelo.request.DeleteFriendApplyRequest;
import com.xcity.pomelo.request.DeleteFriendRequest;
import com.xcity.pomelo.request.GetFriendListRequest;
import com.xcity.pomelo.request.GetFriendRequest;
import com.xcity.pomelo.request.GetFriendsApplyListRequest;
import com.xcity.pomelo.request.GetRecommendFriendsRequest;
import com.xcity.pomelo.request.PresentStrengthRequest;
import com.xcity.pomelo.request.TapLikeRequest;
import com.xcity.pomelo.response.FriendListResponse;
import com.xcity.pomelo.response.HintResponse;
import com.xcity.pomelo.response.PlayerIdResponse;
import com.xcity.pomelo.response.Response;
import com.xcity.pomelo.response.TapLikeResponse;
import com.xcity.util.DBUtils;
import com.xcity.util.PlatformApi;
import com.xcity.util.SessionUtils;

import gnu.trove.set.TLongSet;
import gnu.trove.set.hash.TLongHashSet;
import naga.x.App;
import naga.x.event.Event;
import naga.x.event.EventListener;
import naga.x.game.time.TimeUpdater;
import naga.x.net.Session;
import naga.x.net.annotation.OPHandler;
import naga.x.net.impl.pomelo.annotation.PomeloOP;
import naga.x.service.Service;
import naga.x.service.ServiceException;
import naga.x.util.CacheAdapter;
import naga.x.util.CacheListener;
import naga.x.util.LRUCache;
import naga.x.util.StringUtils;

@OPHandler
public class RelationService implements Service, EventListener {
	
	public static final int CACHE_MAX_SIZE = 1024;

	private Map<Long, Relation> relations; // 只存放好友申请列表
	private Map<Long, Relations> relations2; // 好友、仇人
	private Map<Long, TLongSet> qqFriends; // qq好友
	
	private CacheListener<Relation> relListener = new CacheAdapter<Relation>() {
		@Override
		public void cacheRemoved(Relation val) {
			try {
				val.save();
				LOG.info("[RELATION SAVE]ID[{}]", val.getId());
			} catch (Exception e) {
				LOG.error("[RELATION SAVE ERROR]ID[" + val.getId() + "]", e);
			}
		}
	};
	private CacheListener<Relations> rel2Listener = new CacheAdapter<Relations>() {
		@Override
		public void cacheRemoved(Relations val) {
			val.save();
		}
	};
	protected static final Logger LOG = LoggerFactory.getLogger(RelationService.class);
	
	@Override
	public void startup() throws ServiceException {
		relations = new LRUCache<Long, Relation>(CACHE_MAX_SIZE, relListener);
		relations2 = new LRUCache<Long, Relations>(CACHE_MAX_SIZE, rel2Listener);
		qqFriends = new ConcurrentHashMap<Long, TLongSet>(32);
		App.getApp().getEventManager().register(this);
	}

	@Override
	public void shutdown() throws ServiceException {
		Collection<Relation> vals = relations.values();
		for (Relation val : vals) {
			relListener.cacheRemoved(val);
		}
		Collection<Relations> rels2 = relations2.values();
		for (Relations val : rels2) {
			val.save();
		}
	}
	
	@Override
	public String getId() {
		return getClass().getName();
	}

	@Override
	public int[] getEventTypes() {
		return new int[] {
				GameEvents.EVENT_PLAYER_LOGIN,
				GameEvents.EVENT_PLAYER_LOADED,
				GameEvents.EVENT_PLAYER_LOGOUT,
				GameEvents.EVENT_TAKE_MONEY_IN_FRIEND_BUILD
		};
	}

	@Override
	public void handleEvent(Event event) {
		switch (event.getType()) {
			case GameEvents.EVENT_PLAYER_LOGIN:
				playerLogin(event.getParameter(0));
				break;
			case GameEvents.EVENT_PLAYER_LOADED:
				playerLoaded(event.getParameter(0));
				break;
			case GameEvents.EVENT_PLAYER_LOGOUT:
				playerLogout(event.getParameter(0));
				break;
			case GameEvents.EVENT_TAKE_MONEY_IN_FRIEND_BUILD:
				helpFriendTakeMoney(event.getParameter(0), event.getParameter(1));
				break;
		}
	}
	
	private void playerLogin(final Player player) {
		Relation oldRel = find(player.getId());
		App.getApp().getThreadPool().execute(new Runnable() {
			@Override
			public void run() {
				Relations rels = findRelations(player);
				rels.validate(player);
				moveOldFriends(player, oldRel, rels);
			}
		});
		PlayerUtils.validateCharm(player);
	}
	
	/**
	 * 将老版本的好友数据移动到新版本中
	 * @param oldRel
	 * @param rels
	 */
	private void moveOldFriends(Player player, Relation oldRel, Relations rels) {
		Set<Long> friends = oldRel.getFriends();
		if (friends.isEmpty()) {
			return;
		}
//		Player player = rels.owner;
		PlayerCacheService pcs = App.getApp().getServiceManager().get(PlayerCacheService.class);
		for (Long friendId : friends) {
			Relation2 rel = rels.find(Relation2.TYPE_FRIEND, friendId);
			Player friend;
			if (rel == null && (friend = pcs.find(friendId)) != null) {
				rel = rels.add(Relation2.TYPE_FRIEND, friend);
				Relations friendRels = findRelations(friend);
				friendRels.add(Relation2.TYPE_FRIEND, rel);
				LOG.info("[FRIEND MOVE TO NEW]ID[{}]FRIEND[{}]", player.getId(), friendId);
			}
		}
		friends.clear();
	}
	
	private void playerLoaded(Player player) {
		AccountChannel ac = player.getAccount().getChannel();
		if (ac != AccountChannel.QQ_WANBA && ac != AccountChannel.QQ) {
			return;
		}
		List<Player> friends = PlatformApi.getQQFriends(player);
		if (friends == null || friends.isEmpty()) {
			return;
		}
		TLongSet friendIds = new TLongHashSet();
		for (Player friend : friends) {
			friendIds.add(friend.getId());
		}
		qqFriends.put(player.getId(), friendIds);
		QQFriendsRankingList qqFriendsRankingList = RankingListEnum.QQ_FRIENDS_COMBAT_POWER.castInstance();
		qqFriendsRankingList.firePlayerLoaded(player, friends);
	}
	
	private void playerLogout(Player player) {
//		Relations rels = findRelations(player, true);
//		if (rels != null) {
//			rels.owner = null; // gc
//		}
		
		AccountChannel ac = player.getAccount().getChannel();
		if (ac != AccountChannel.QQ_WANBA && ac != AccountChannel.QQ) {
			return;
		}
		qqFriends.remove(player.getId());
		QQFriendsRankingList qqFriendsRankingList = RankingListEnum.QQ_FRIENDS_COMBAT_POWER.castInstance();
		qqFriendsRankingList.firePlayerLogout(player);
	}
	
	private void helpFriendTakeMoney(Player player, Build friendBuild) {
		Relations rels = findRelations(player);
		Player friend = friendBuild.getManager().getOwner();
		Relation2 rel = rels.find(Relation2.TYPE_FRIEND, friend.getId());
		if (rel == null) {
			// 不是好友(并发有可能导致)
			LOG.info("[ADD FRIEND INTIMACY ERROR]ID[{}]FRIEND[{}]CAUSE[{}]", player.getId(), friend.getId(), Cause.HELP_FRIEND);
			return;
		}
		rel.addIntimacy(Relation2.HELP_FRIEND_ADD_INTIMACY, Cause.HELP_FRIEND);
	}
	
	public Relation find(long playerId) {
		Relation relation = relations.get(playerId);
		if (relation != null) {
			return relation;
		}
		relation = DBUtils.find(playerId, RelationMapper.class);
		if (relation == null) {
			relation = new Relation(playerId);
		}
		relations.put(playerId, relation);
		return relation;
	}
	
	public Relations findRelations(long playerId) {
		return findRelations(playerId, false);
	}
	
	public Relations findRelations(long playerId, boolean notForce) {
		Player player = App.getApp().getServiceManager().get(PlayerCacheService.class).find(playerId);
		if (player == null) {
			return null;
		}
		return findRelations(player, notForce);
	}
	
	public Relations findRelations(Player player) {
		return findRelations(player, false);
	}
	
	public Relations findRelations(Player player, boolean notForce) {
		Long playerId = Long.valueOf(player.getId());
		Relations rels = relations2.get(playerId);
		if (rels != null) {
//			rels.owner = player;
			return rels;
		}
		if (notForce) {
			return null;
		}
		List<Relation2> friends = DBUtils.selectList(Relation2Mapper.STATEMENT_FIND_BY, DBUtils.wrapParams("type", Relation2.TYPE_FRIEND, "playerId", playerId));
		rels = new Relations(player, friends, this);
		// 合并内存中relations2的数据(如果内存中有对应的relation则将其替换为内存中的数据，如果没有则将其添加到内存)
		// 保证两个玩家之间的relation相同
		rels.merge(relations2);
		relations2.put(playerId, rels);
		return rels;
	}
	
	@PomeloOP(PomeloRoute.ADD_FRIEND_BY_SHARE)
	protected void addFriendByShare(Session session, AddFriendByShareRequest req) {
//		TODO
//		Player player = session.getClient();
//		String error = addFriend(req.getFromUser(), player.getId());
//		if (error != null) {
//			session.send(new HintResponse(req.getSerial(), error));
//			return;
//		}
		session.send(new Response(req.getSerial()));
	}
	
	@PomeloOP(PomeloRoute.APPLY_ADD_FRIEND)
	protected void requestApplyAddFriend(Session session, ApplyAddFriendRequest req) {
		Player player = session.getClient();
		Relations rels = findRelations(player);
		// 检测自己的好友上限
		int max = PlayerUtils.getFriendsNumMax(player);
		if (rels.sizeOf(Relation2.TYPE_FRIEND) >= max) {
			session.send(new HintResponse(req.getSerial(), MessageConst.FRIENDS_NUM_MAXED));
			return;
		}
		if (rels.find(Relation2.TYPE_FRIEND, req.getRoleId()) != null) {
			session.send(new HintResponse(req.getSerial(), MessageConst.ALREADY_BE_FRIEND));
			return;
		}
		Relation oldRel = find(player.getId());
		if (oldRel.getFriendsApply().size() >= max) {
			session.send(new HintResponse(req.getSerial(), MessageConst.FRIENDS_APPLY_NUM_MAXED));
			return;
		}
		Player dest;
		if (req.getRoleId() == 0L
				|| (dest = App.getApp().getServiceManager().get(PlayerCacheService.class).find(req.getRoleId())) == null) {
			session.send(new HintResponse(req.getSerial(), MessageConst.PLAYER_NOT_EXIST));
			return;
		}
		Relation destOldRel = find(req.getRoleId());
		if (destOldRel.isInApplyList(player.getId())) {
			session.send(new HintResponse(req.getSerial(), MessageConst.FRIEND_HAS_APPLIED));
			return;
		}
		if (destOldRel.getFriendsApply().size() >= max) {
			session.send(new HintResponse(req.getSerial(), MessageConst.TARGET_PLAYER_FRIENDS_APPLY_NUM_MAXED));
			return;
		}
		// 检测对方好友数量是否已达上限
		Relations destRel = findRelations(dest);
		max = PlayerUtils.getFriendsNumMax(dest);
		if (destRel.sizeOf(Relation2.TYPE_FRIEND) >= max) {
			session.send(new HintResponse(req.getSerial(), MessageConst.TARGET_PLAYER_FRIENDS_NUM_MAXED));
			return;
		}
		
		destOldRel.addFriendApply(player.getId());
		if (dest.isOnline()) {
			dest.send(new FriendApplyPush(player.getId(), player.getName()));
			dest.send(DotTipPush.NEW_FRIEND_APPLY);
		}
		session.send(new Response(req.getSerial()));
	}
	
	@PomeloOP(PomeloRoute.ADD_FRIEND)
	protected void requestAddFriend(Session session, AddFriendRequest req) {
		Player player = session.getClient();
		if (!req.isAccept()) {
			Relation srcRel = find(player.getId());
			Relation destRel = find(req.getRoleId());
			destRel.removeFriendApply(player.getId());
			srcRel.removeFriendApply(req.getRoleId());
			session.send(new Response(req.getSerial()));
			return;
		}
		String error = addFriend(player.getId(), req.getRoleId());
		if (error != null) {
			session.send(new HintResponse(req.getSerial(), error));
			return;
		}
		session.send(new PlayerIdResponse(req.getSerial(), req.getRoleId()));
	}
	
	private String addFriend(long srcPlayerId, long destPlayerId) {
		PlayerCacheService as = App.getApp().getServiceManager().get(PlayerCacheService.class);
		Player dest;
		if (destPlayerId == 0L
				|| (dest = as.find(destPlayerId)) == null) {
			return MessageConst.PLAYER_NOT_EXIST;
		}
		Relation destOldRel = find(destPlayerId);
		Player src = as.find(srcPlayerId);
		if (src == null) {
			return MessageConst.ADD_FRIEND_FAIL;
		}
		Relation srcOldRel = find(srcPlayerId);
		destOldRel.removeFriendApply(srcPlayerId);
		srcOldRel.removeFriendApply(destPlayerId);
		if (isFriend(src, dest)) {
			return MessageConst.ALREADY_BE_FRIEND;
		}
		// 检测自己的好友数量是否已达上限
		Relations srcRel = findRelations(src);
		int max = PlayerUtils.getFriendsNumMax(src);
		if (srcRel.sizeOf(Relation2.TYPE_FRIEND) >= max) {
			return MessageConst.FRIENDS_NUM_MAXED;
		}
		// 检测对方的好友数量是否已达上限
		Relations destRel = findRelations(dest);
		max = PlayerUtils.getFriendsNumMax(dest);
		if (destRel.sizeOf(Relation2.TYPE_FRIEND) >= max) {
			return MessageConst.TARGET_PLAYER_FRIENDS_NUM_MAXED;
		}
		// 互加好友
		Relation2 relation = srcRel.add(Relation2.TYPE_FRIEND, dest);
		destRel.add(Relation2.TYPE_FRIEND, relation);
		LOG.info("[ADDFRIEND]SRC[{}]DEST[{}]", srcPlayerId, destPlayerId);
		Player target = App.getApp().getServiceManager().get(PlayerService.class).getOnlinePlayer(destPlayerId);
		if(target != null){
			App.getApp().getEventManager().addEvent(GameEvents.EVENT_ADD_FRIEND, target);
		}
		if(src != null){
			App.getApp().getEventManager().addEvent(GameEvents.EVENT_ADD_FRIEND, src);
		}
		return null;
	}
	
	@PomeloOP(PomeloRoute.GET_FRIEND)
	protected void requestFindFriend(Session session, GetFriendRequest req) {
		if (StringUtils.isEmpty(req.getRoleName())) {
			session.send(new HintResponse(req.getSerial(), MessageConst.ILLEGAL_OPERATION));
			return;
		}
		Player player = session.getClient();
		Relations rels = findRelations(player);
//		List<Relation2> friends = rels.getRelations(Relation2.TYPE_FRIEND);
//		int size = friends.size();
		int size = rels.sizeOf(Relation2.TYPE_FRIEND);
		if (size > 0) {
			final PlayerCacheService pcs = App.getApp().getServiceManager().get(PlayerCacheService.class);
			final List<Player> friends = new ArrayList<Player>();
			rels.forEach(Relation2.TYPE_FRIEND, new Consumer<Relation2>() {
				@Override
				public void accept(Relation2 rel) {
					Player friend = pcs.find(rels.getDestId(rel));
					if (friend != null && req.getRoleName().equals(friend.getName())) {
						friends.add(friend);
					}
				}
			});
			if (friends.size() > 0) {
				session.send(new FriendListResponse(req.getSerial(), player, rels, friends));
				return;
			}
		}
		session.send(new HintResponse(req.getSerial(), MessageConst.FRIEND_NOT_EXIST));
	}
	
	@PomeloOP(PomeloRoute.DELETE_FRIEND_APPLY)
	protected void requestDeleteFriendApply(Session session, DeleteFriendApplyRequest req) {
		if (req.getRoleId() == 0L) {
			session.send(new HintResponse(req.getSerial(), MessageConst.PLAYER_NOT_EXIST));
			return;
		}
		Player player = session.getClient();
		Relation rel = find(player.getId());
		rel.removeFriendApply(req.getRoleId());
		session.send(new PlayerIdResponse(req.getSerial(), req.getRoleId()));
	}
	
	@PomeloOP(PomeloRoute.DELETE_FRIEND)
	protected void requestDeleteFriend(Session session, DeleteFriendRequest req) {
		if (req.getRoleId() == 0L) {
			session.send(new HintResponse(req.getSerial(), MessageConst.PLAYER_NOT_EXIST));
			return;
		}
		// 删除对方
		Player player = session.getClient();
		Relations rels = findRelations(player);
		Relation2 rel = rels.remove(Relation2.TYPE_FRIEND, req.getRoleId());
		// 从对方好友列表删除自己
		if (rel != null && (rels = findRelations(req.getRoleId(), true)) != null) {
			rels.remove(Relation2.TYPE_FRIEND, player.getId());
		}
		session.send(new PlayerIdResponse(req.getSerial(), req.getRoleId()));
		LOG.info("[DELFRIEND]ID[{}]FRIEND[{}]", player.getId(), req.getRoleId());
	}
	
	@PomeloOP(PomeloRoute.GET_FRIEND_LIST)
	protected void requestFriendList(Session session, GetFriendListRequest req) {
		Player player = session.getClient();
		Relations rels = findRelations(player);
//		List<Relation2> friendRels = rels.getRelations(Relation2.TYPE_FRIEND);
//		int size = friendRels.size();
		int size = rels.sizeOf(Relation2.TYPE_FRIEND);
		if (size < 1) {
			session.send(new FriendListResponse(req.getSerial(), player, rels, Collections.emptyList()));
			return;
		}
		final List<Player> friends = new ArrayList<Player>(size);
		final PlayerCacheService pcs = App.getApp().getServiceManager().get(PlayerCacheService.class);
		rels.forEach(Relation2.TYPE_FRIEND, new Consumer<Relation2>() {
			@Override
			public void accept(Relation2 rel) {
				Player friend = pcs.find(rels.getDestId(rel));
				if (friend != null) {
					friends.add(friend);
				}
			}
		});
		session.send(new FriendListResponse(req.getSerial(), player, rels, friends));
	}
	
	@PomeloOP(PomeloRoute.GET_FRIEND_APPLY_LIST)
	protected void requestFriendApplyList(Session session, GetFriendsApplyListRequest req) {
		Player player = session.getClient();
		Relation rel = find(player.getId());
		Set<Long> friendIds = rel.getFriendsApply();
		List<Player> friends = new ArrayList<Player>(friendIds.size());
		PlayerCacheService as = App.getApp().getServiceManager().get(PlayerCacheService.class);
		for (Long f : friendIds) {
			Player actor = as.find(f);
			if (actor != null) {
				friends.add(actor);
			}
		}
		session.send(new FriendListResponse(req.getSerial(), player, null, friends));
	}
	
	/**
	 * 附近
	 * @param session
	 * @param req
	 */
	@PomeloOP(PomeloRoute.GET_NEIGHBOURS)
	protected void requestNeighbours(Session session, GetRecommendFriendsRequest req) {
		if (req.getIndex() <= 0 || req.getNum() <= 0) {
			session.send(new HintResponse(req.getSerial(), MessageConst.ILLEGAL_OPERATION));
			return;
		}
		Player player = session.getClient();
		int count = req.getNum() > 100 ? 100 : req.getNum();
		int offset = (req.getIndex() - 1) * count;
		int maxPage = (RankingListEnum.LEVEL.instance.size() + count - 1) / count;
		List<Ranking> rankings = RankingListEnum.LEVEL.instance.getEntries();
		List<Player> players = new ArrayList<Player>();
		PlayerCacheService as = App.getApp().getServiceManager().get(PlayerCacheService.class);
		for (int i = offset; i < offset + count && i < rankings.size(); i++) {
			Ranking r = rankings.get(i);
			Player actor = as.find(r.getPlayerId());
			if (actor != null && actor.getId() != player.getId()) {
				players.add(actor);
			}
		}
		if (!player.getPool().getBooleanValue(PlayerPool.PROPERTY_ROBBED_ROBOT)) {
			// 新手引导 加入机器人
			players.add(0, RobotUtils.getDefault());
			if (players.size() > count) {
				players.remove(players.size() - 1);
			}
		}
		FriendListResponse res = new FriendListResponse(req.getSerial(), session.getClient(), null, players);
		res.setMax(maxPage); // 这里当做最大页数
		session.send(res);
	}
	
	@PomeloOP(PomeloRoute.BATCH_AGREE_FRIEND_APPLY)
	protected void batchAgreeApply(Session session, BatchAgreeFriendApplyRequest req) {
		Player player = session.getClient();
		Relation rel = find(player.getId());
		if (rel.getFriendsApply().size() == 0) {
			session.send(new HintResponse(req.getSerial(), MessageConst.HAS_NO_FRIEND_APPLY));
			return;
		}
		Long[] vals = rel.getFriendsApply().toArray(new Long[rel.getFriendsApply().size()]);
		StringBuilder LOGIDS = new StringBuilder();
		for (Long val : vals) {
			addFriend(player.getId(), val);
			LOGIDS.append(val).append(',');
		}
		LOGIDS.setLength(LOGIDS.length() - 1);
		session.send(new Response(req.getSerial()));
		LOG.info("[BATCH ADDFRIEND]ID[{}]FRIENDS[{}]", player.getId(), LOGIDS.toString());
	}
	
	@PomeloOP(PomeloRoute.BATCH_REFUSE_FRIEND_APPLY)
	protected void batchRefuseApply(Session session, BatchRefuseFriendApplyRequest req) {
		Player player = session.getClient();
		Relation rel = find(player.getId());
		if (rel.getFriendsApply().size() == 0) {
			session.send(new HintResponse(req.getSerial(), MessageConst.HAS_NO_FRIEND_APPLY));
			return;
		}
		rel.getFriendsApply().clear();
		session.send(new Response(req.getSerial()));
	}
	
	@PomeloOP(PomeloRoute.TAP_LIKE)
	protected void requestTapLike(Session session, TapLikeRequest req) {
		Player player = session.getClient();
		if (req.getTarget() == player.getId()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.CAN_NOT_TAP_LIKE_FOR_SELF));
			return;
		}
		if (PlayerUtils.getLeftTapLikeTimes(player) <= 0) {
			session.send(new HintResponse(req.getSerial(), MessageConst.TAP_LIKE_TIMES_ZERO));
			return;
		}
		if (PlayerUtils.wasTappedLike(player, req.getTarget())) {
			session.send(new HintResponse(req.getSerial(), MessageConst.TAPPED_LIKE));
			return;
		}
		Player target;
		if (req.getTarget() == 0L || (target = App.getApp().getServiceManager().get(PlayerCacheService.class).find(req.getTarget())) == null) {
			session.send(new HintResponse(req.getSerial(), MessageConst.PLAYER_NOT_EXIST));
			return;
		}
		Relations rels = findRelations(player);
		Relation2 rel = rels.find(Relation2.TYPE_FRIEND, target.getId());
		if (rel == null) {
			session.send(new HintResponse(req.getSerial(), MessageConst.TAP_LIKE_ONLY_TO_FRIEND));
			return;
		}
		PlayerUtils.tapLike(player, target, rel);
		session.send(new TapLikeResponse(req.getSerial(), PlayerUtils.getCharm(player), target.getId(), PlayerUtils.getCharm(target), rel != null ? rel.getIntimacy() : 0, PlayerUtils.getLeftTapLikeTimes(player)));
		App.getApp().getServiceManager().get(MailService.class).sendSystemMail(
				target.getId(),
				MessageConst.BE_TAPPED_LIKE_MAIL_TITLE,
				MessageFormat.format(MessageConst.BE_TAPPED_LIKE_MAIL_CONTENT, player.getName(), player.getId(), new Date()));
		if (target.isOnline()) {
			target.send(new TipPush(MessageFormat.format(MessageConst.BE_TAPPED_LIKE, player.getName())));
		}
		LOG.info("[TAPLIKE]ID[{}]TARGET[{}]", player.getId(), target.getId());
	}
	
	@PomeloOP(PomeloRoute.PRESENT_STRENGTH)
	protected void requestPresentStrength(Session session, PresentStrengthRequest req) {
		Player target;
		if (req.getId() == 0L || (target = App.getApp().getServiceManager().get(PlayerCacheService.class).find(req.getId())) == null) {
			session.send(new HintResponse(req.getSerial(), MessageConst.PLAYER_NOT_EXIST));
			return;
		}
		Player player = session.getClient();
		String error = PlayerUtils.presentStrength(this, player, target);
		if (error != null) {
			session.send(new HintResponse(req.getSerial(), error));
			return;
		}
		if (target.isOnline()) {
			target.send(new TipPush(MessageFormat.format(MessageConst.BE_PRESENTED_STRENGTH_HINT, player.getName(), GameStaticConfig.presentStr)));
		}
		session.send(new Response(req.getSerial()));
		LOG.info("[PRESENT STR]ID[{}]TARGET[{}]", player.getId(), target.getId());
	}
	
	@PomeloOP(PomeloRoute.ASK_PRESENT_STRENGTH)
	protected void requestAskPresentStrength(Session session, AskPresentStrengthRequest req) {
		Player target;
		if (req.getId() == 0L || (target = App.getApp().getServiceManager().get(PlayerCacheService.class).find(req.getId())) == null) {
			session.send(new HintResponse(req.getSerial(), MessageConst.PLAYER_NOT_EXIST));
			return;
		}
		Player player = session.getClient();
		if (PlayerUtils.wasPresentedStrength(target, player.getId())) {
			session.send(new HintResponse(req.getSerial(), MessageConst.HAVE_BE_PRESENTED_STRENGTH));
			return;
		}
		if (!isFriend(player, target)) {
			session.send(new HintResponse(req.getSerial(), MessageConst.NOT_FRIENDS));
			return;
		}
		int maxVal = PlayerUtils.getFriendsNumMax(target) * GameStaticConfig.presentStr;
		if (PlayerUtils.getBePresentedStrength(player) >= maxVal) {
			session.send(new HintResponse(req.getSerial(), MessageFormat.format(MessageConst.BE_PRESENTED_STRENGTH_MAXED, maxVal)));
			return;
		}
		String sessKey = SessionUtils.ASK_FRIEND_PRESENT_STRENGTH_PREFIX + target.getId();
		Long lastTime = session.getAttribute(sessKey);
		if (lastTime != null && TimeUpdater.getInstance().now() - lastTime <= 3600000) {
			session.send(new HintResponse(req.getSerial(), MessageConst.ASK_DUPLICATED));
			return;
		}
		session.setAttribute(sessKey, TimeUpdater.getInstance().now());
		App.getApp().getServiceManager().get(MailService.class).sendSystemMail(
				target.getId(),
				MessageConst.ASK_PRESENT_STRENGTH_MAIL_TITLE,
				MessageFormat.format(MessageConst.ASK_PRESENT_STRENGTH_MAIL_CONTENT, player.getName(), player.getId(), GameStaticConfig.presentStr));
		if (target.isOnline()) {
			new AskPresentStrengthOperation(target, player).send();
		}
		session.send(new Response(req.getSerial()));
	}
	
	public boolean isFriend(long player1Id, long player2Id) {
		return isFriend(player1Id, player2Id, false);
	}
	
	public boolean isFriend(long player1Id, long player2Id, boolean qqCheck) {
		PlayerCacheService pcs = App.getApp().getServiceManager().get(PlayerCacheService.class);
		Player p1 = pcs.find(player1Id);
		if (p1 == null) {
			return false;
		}
		Player p2 = pcs.find(player2Id);
		if (p2 == null) {
			return false;
		}
		return isFriend(p1, p2, qqCheck);
	}
	
	public boolean isFriend(Player p1, Player p2) {
		return isFriend(p1, p2, false);
	}
	
	public boolean isFriend(Player p1, Player p2, boolean qqCheck) {
		Player src, dst;
		if (p1.isOnline()) {
			src = p1;
			dst = p2;
		} else {
			src = p2;
			dst = p1;
		}
		Relations rels = findRelations(src);
		Relation2 rel = rels.find(Relation2.TYPE_FRIEND, dst.getId());
		if (rel != null) {
			return true;
		}
		// 检测qq好友关系
		return qqCheck && isQQFriend(src, dst);
	}
	
	public boolean isQQFriend(Player src, Player dst) {
		User acc = src.getAccount();
		if (acc == null
				|| (acc.getChannel() != AccountChannel.QQ_WANBA
					&& acc.getChannel() != AccountChannel.QQ)) {
			return false;
		}
		TLongSet qqRelations = qqFriends.get(src.getId());
		return qqRelations != null && qqRelations.contains(dst.getId());
	}

}
